package IC.AST;

import IC.AST.nodes.*;
import IC.Parser.sym;


public class ASTPrinter implements PropogatingVisitor<Object,Object> {

	protected		ASTNode 	root;
	protected	int 		depth=0;
	protected	static 	String SPACES = "                                                     ";
	protected	static 	final int ident = 4;

	public ASTPrinter(ASTNode symbol) {
		this.root = symbol;
	}

	/**
	 * enter a recursion level - print the header and increment depth
	 * @param header the object title
	 */
	protected void enter(String header, ASTNode node) {
		System.out.printf("%3d:",node.getLine(),node.getColumn());
		System.out.println(ASTPrinter.SPACES.substring(0,depth*ident)+""+header);
		depth ++;
	}

	/**
	 * exit a recursion level
	 * print closing brcket and derement depth
	 */
	protected void leave() {
		depth --;
		//	System.out.println(ASTPrinter.SPACES.substring(0,depth*ident)+" ");
	}

	/**
	 * print non-recursive item
	 * @param header
	 */
	protected void enterLeave(String header, ASTNode node) {
		System.out.printf("%3d:",node.getLine(),node.getColumn());
		System.out.println(ASTPrinter.SPACES.substring(0,depth*ident)+"{"+header+"}");

	}


	public void print() throws Exception {
		depth = 1;
		root.accept(this,null);
	}

	/*****************************
	 * visit functions
	 */

	public Object visit(Program that, Object d) throws Exception {
		enter("Program(classes)",that);
		for (ClassDecl cls: that.getClasses()) {
			cls.accept(this,null);
		}
		leave();
		return null;
	}

	public Object visit(ArrayTypeDecl that, Object d) throws Exception {
		enter("ArrayTypeDecl(elem type)",that);
		that.getElemType().accept(this,null);
		leave();
		return null;
	}

	public Object visit(ClassDecl that, Object d) throws Exception {
		enter("ClassDecl(members) "+that.getClassID()+" extends:"+that.getBaseClassID(),that);
		for (ASTNode cmd : that.getClassMembers()) {
			cmd.accept(this,null);
		}
		leave();
		return null;
	}

	public Object visit(ClassTypeDecl that, Object d) throws Exception {
		enterLeave("ClassTypeDecl class:"+that.getClassID(),that);
		return null;
	}

	public Object visit(FieldsDecl that, Object d) throws Exception {
		StringBuffer s = new StringBuffer("FieldsDecl(type) ");
		for(String id : that.getIDs()) {
			s.append(id);
			s.append(" ");
		}
		enter(s.toString(),that);
		that.getType().accept(this,null);
		leave();
		return null;
	}

	public Object visit(FormalDecl that, Object d) throws Exception {
		enter("FormalDecl "+that.getID(),that);
		that.getType().accept(this,null);
		leave();
		return null;
	}

	public Object visit(MethodDecl that, Object d) throws Exception {
		StringBuffer s = new StringBuffer("MethodDecl(rettype,formals,body) ");
		s.append(that.getMethodName());
		if(that.isStatic()) s.append(" static");
		enter(s.toString(),that);

		if(that.getReturnType() != null) {
			that.getReturnType().accept(this,null);
		} else { 
			enterLeave("void",that);
		}

		for(FormalDecl fd : that.getFormals()) {
			fd.accept(this,null);
		} 

		if(that.getBody() != null) {
			that.getBody().accept(this,null);
		} else {
			enterLeave("null", that);
		}

		leave();
		return null;
	}

	public Object visit(PrimitiveTypeDecl that, Object d) throws Exception {
		switch (that.getKind()) {
		case sym.INT: enterLeave("PrimitiveTypeDecl kind:int",that); break; 
		case sym.BOOLEAN: enterLeave("PrimitiveTypeDecl kind:boolean",that); break; 
		case sym.STRING: enterLeave("PrimitiveTypeDecl kind:string",that); break; 
		default:
			enterLeave("PrimitiveTypeDecl unknown:"+that.getKind(),that); break; 
		}
		return null;
	}

	public Object visit(ArrayElmVar that, Object d) throws Exception {
		enter("ArrayElementVar(array,index)",that);
		that.getArray().accept(this,null);
		that.getIndex().accept(this,null);
		leave();
		return null;
	}

	public Object visit(BinaryExpr that, Object d) throws Exception {
		enter("BinaryExpr(left,right) op:"+that.getOp(),that);
		that.getLeft().accept(this,null);
		that.getRight().accept(this,null);
		leave();
		return null;
	}

	public Object visit(BoolLiteral that, Object d) throws Exception {
		enterLeave("BoolLiteral value:"+that.getValue(),that);
		return null;
	}


	public Object visit(StaticCall that, Object d) throws Exception {
		enter("StaticCall(args) classID:"+that.getClassID()+" method:"+that.getMethodID(),that);
		for ( Expression arg : that.getArgs()) {
			arg.accept(this,null);
		}
		leave();
		return null;
	}


	public Object visit(VirtualCall that, Object d) throws Exception {
		enter("VirtualCall(instance,args) method:"+that.getMethodID(),that);
		if (that.getInstance() != null) {
			that.getInstance().accept(this,null);
		} else {
			enterLeave("null", that);
		}
		for ( Expression arg : that.getArgs()) {
			arg.accept(this,null);
		}
		leave();
		return null;
	}


	public Object visit(IntLiteral that, Object d) throws Exception {
		enterLeave("IntLiteral value:"+that.getValue(),that);
		return null;
	}


	public Object visit(LengthExpr that, Object d) throws Exception {
		enter("LengthExpr(array)",that);
		that.getArray().accept(this,null);
		leave();
		return null;
	}


	public Object visit(SimpleVar that, Object d) throws Exception {
		enterLeave("SimpleVar id:"+that.getID(),that);
		return null;
	}


	public Object visit(MemberVar that, Object d) throws Exception {
		enter("MemberVar(instance) id:"+that.getID(),that);
		that.getInstance().accept(this,null);
		leave();
		return null;
	}


	public Object visit(NewArrayExpr that, Object d) throws Exception {
		enter("NewArrayExpr(type,size)",that);
		that.getElemType().accept(this,null);
		that.getSize().accept(this,null);
		leave();
		return null;
	}


	public Object visit(NewClassExpr that, Object d) throws Exception {
		enterLeave("NewClassExpr clsid:"+that.getClassID(),that);
		return null;
	}


	public Object visit(NullLiteral that, Object d) throws Exception {
		enterLeave("NullLiteral",that);
		return null;
	}


	public Object visit(StringLiteral that, Object d) throws Exception {
		enterLeave("StringLiteral value:\"" +
				that.getValue().toString() // print escaped string
				.replace("\\", "\\\\")
				.replace("\"", "\\\"")
				.replace("\n", "\\n")
				.replace("\t", "\\t") +
				"\"",that);
		return null;
	}


	public Object visit(ThisExpr that, Object d) throws Exception {
		enterLeave("ThisExpr",that);
		return null;
	}


	public Object visit(UnaryExpr that, Object d) throws Exception {
		enter("UnaryExpr(op,operand)"+that.getOp(),that);
		that.getOperand().accept(this,null);
		leave();		
		return null;
	}


	public Object visit(AssignStmt that, Object d) throws Exception {
		enter("AssignStmt(src,dest)",that);
		that.getSrc().accept(this,null);
		that.getDest().accept(this,null);
		leave();
		return null;
	}


	public Object visit(StmtBlock that, Object d) throws Exception {
		enter("StmtBlock(statements)",that);
		for(Statement stmt : that.getStmts()) {
			stmt.accept(this,null);
		} 
		leave();
		return null;
	}


	public Object visit(BreakStmt that, Object d) throws Exception {
		enterLeave("BreakStmt",that);
		return null;
	}


	public Object visit(ContinueStmt that, Object d) throws Exception {
		enterLeave("ContinueStmt",that);
		return null;
	}


	public Object visit(IfStmt that, Object d) throws Exception {
		enter("IfStmt(condition,then,else)",that);
		that.getCondition().accept(this,null);
		that.getIfStmt().accept(this,null);
		if (that.getElseStmt() != null) {
			that.getElseStmt().accept(this,null);
		} else {
			enterLeave("null", that);
		}
		leave();
		return null;
	}


	public Object visit(ReturnStmt that, Object d) throws Exception {
		enter("Return(expr)",that);
		if(that.getExpr() != null) {
			that.getExpr().accept(this,null);
		} else {
			enterLeave("null", that);
		}
		leave();
		return null;
	}


	public Object visit(VarDeclStmt that, Object d) throws Exception {
		enter("VarDeclStmt(type,init) id:"+that.getVar().getID(),that);
		that.getType().accept(this,null);
		if (that.getInit() != null) {
			that.getInit().accept(this,null);
		} else {
			enterLeave("null", that);
		}
		leave();
		return null;
	}


	public Object visit(WhileStmt that, Object d) throws Exception {
		enter("WhileStmt(cond,body)",that);
		that.getCondition().accept(this,null);
		that.getBody().accept(this,null);
		leave();
		return null;
	}

	public Object visit(MethodCallStmt that, Object d) throws Exception {
		enter("MethodCallStmt(call)",that);
		if(that.getCall() != null) {
			that.getCall().accept(this,null);
		} else {
			enterLeave("null", that);
		}
		leave();
		return null;
	}
}
